﻿using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using SL.M;
using SL.M.Net;

namespace SL.M.ManagedTest
{
    [TestClass]
    public class NetTest
    {
        const int SpinTime = 1000;

        class NetTestObject : GameObject
        {
            public string StringProp
            {
                get;
                set;
            }

            public NetTestObject()
            {

            }

            protected override void Read(Archiver archiver)
            {
                base.Read(archiver);
                StringProp = archiver.ReadString("StringProp");
            }

            protected override void Write(Archiver archiver)
            {
                base.Write(archiver);
                archiver.WriteValue("StringProp", StringProp);
            }
        }

        class NetAuthObject : GameObject
        {
            public bool AuthFailure
            {
                get;
                set;
            }

            protected override void Read(Archiver archiver)
            {
                AuthFailure = archiver.ReadBool("AuthFailure");
                base.Read(archiver);
            }

            protected override void Write(Archiver archiver)
            {
                archiver.WriteValue("AuthFailure", AuthFailure);
                base.Write(archiver);
            }
        }

        class ClientServerProcess : Process
        {
            public int ClientFrame = 0;
            public int ServerFrame = 0;
            public int ClientAccepted = 0;
            public int ServerStopped = 0;
            public int ClientStopped = 0;

            public List<GameServerSession<NetTestObject>> Sessions = new List<GameServerSession<NetTestObject>>();

            protected override void ProcessEntry()
            {
                while (Started)
                {
                    NextMessage();
                }
            }

            protected override bool HandleMessage(Message message)
            {
                if (message.TypeEquals("us.sl.mgame.serverFrame") && message.Sender is GameClient<NetTestObject>)
                {
                    ++ClientFrame;
                    return true;
                }
                if (message.TypeEquals("us.sl.mgame.serverFrame") && message.Sender is GameServer<NetTestObject>)
                {
                    ++ServerFrame;
                    return true;
                }
                if (message.TypeEquals("us.sl.mgame.clientAccepted") && message.Sender is GameClient<NetTestObject>)
                {
                    ++ClientAccepted;
                    return true;
                }
                if (message.TypeEquals("us.sl.mgame.clientAccepted") && message.Sender is GameServerSession<NetTestObject>)
                {
                    Sessions.Add((GameServerSession<NetTestObject>)message.Sender);
                    return true;
                }
                if (message.TypeEquals("us.sl.m.stop") && message.Sender is GameServerSession<NetTestObject>)
                {
                    Sessions.Remove((GameServerSession<NetTestObject>)message.Sender);
                    return true;
                }
                if (message.TypeEquals("us.sl.m.stop") && message.Sender is GameServer<NetTestObject>)
                {
                    ++ServerStopped;
                    return true;
                }
                if (message.TypeEquals("us.sl.m.stop") && message.Sender is GameClient<NetTestObject>)
                {
                    ++ClientStopped;
                    return true;
                }

                return base.HandleMessage(message);
            }
        }

        ManualResetEventSlim _connectEvent = new ManualResetEventSlim();

        [TestMethod]
        public void ManagedTestConnectionManager()
        {
            TcpListener listener = new TcpListener(IPAddress.Loopback, 6001);
            listener.Start();

            foreach (SessionFlags flags in new SessionFlags[] { SessionFlags.None, SessionFlags.EnableCompression, SessionFlags.EnableSecureChannel, SessionFlags.EnableCompression | SessionFlags.EnableSecureChannel })
            {
                TcpClient client = new TcpClient();
                IAsyncResult connectResult = client.BeginConnect(IPAddress.Loopback, 6001, ConnectComplete, null);
                TcpClient server = listener.AcceptTcpClient();
                _connectEvent.Wait();
                _connectEvent.Reset();

                Task<ConnectionManager> clientCmTask = Task.Factory.StartNew<ConnectionManager>(() => new ConnectionManager(Dns.GetHostName(), client, flags, false));
                Task<ConnectionManager> serverCmTask = Task.Factory.StartNew<ConnectionManager>(() => new ConnectionManager(Dns.GetHostName(), server, flags, true));
                ConnectionManager clientCm = clientCmTask.Result;
                ConnectionManager serverCm = serverCmTask.Result;

                NetTestObject clientObj = new NetTestObject();
                clientObj.StringProp = "foo";
                NetTestObject serverObj = new NetTestObject();

                Task sendTask = Task.Factory.StartNew(() => clientCm.SendFrame(clientObj));
                Task recieveTask = Task.Factory.StartNew(() => serverCm.RecieveFrame(serverObj));
                Task.WaitAll(sendTask, recieveTask);
                Assert.AreEqual("foo", serverObj.StringProp);
                serverObj.StringProp = "bar";
                sendTask = Task.Factory.StartNew(() => serverCm.SendFrame(serverObj));
                recieveTask = Task.Factory.StartNew(() => clientCm.RecieveFrame(clientObj));
                Task.WaitAll(sendTask, recieveTask);
                Assert.AreEqual("bar", clientObj.StringProp);
            }

            listener.Stop();
        }

        [TestMethod]
        public void ManagedTestGameClientServer()
        {
            Game g = new Game();
            g.Start();

            foreach (SessionFlags flags in new SessionFlags[] { SessionFlags.None, SessionFlags.EnableCompression, SessionFlags.EnableSecureChannel, SessionFlags.EnableCompression | SessionFlags.EnableSecureChannel })
            {
                GameServer<NetTestObject> server = new GameServer<NetTestObject>(new IPEndPoint[] { new IPEndPoint(IPAddress.Any, 6001), new IPEndPoint(IPAddress.IPv6Any, 6001) }, new NetTestObject(), () => new NetAuthObject(), (o) => { if (((NetAuthObject)o).AuthFailure) throw new SessionAbortedException("auth"); }, 1, flags);
                GameClient<NetTestObject> client = new GameClient<NetTestObject>(Dns.GetHostName(), 6001, new NetTestObject(), () => new NetAuthObject(), flags);
                ClientServerProcess listener = new ClientServerProcess();
                g.AddProcess(listener);
                g.AddProcess(server);
                Assert.AreEqual(0, listener.ClientAccepted);
                g.AddProcess(client);

                Thread.Sleep(SpinTime);
                Assert.IsFalse(client.IsFaulted);

                Thread.Sleep(SpinTime);
                Assert.IsTrue(listener.ClientFrame > 0 && listener.ServerFrame > 0);
                Assert.IsTrue(listener.Sessions.Count > 0);

                Assert.AreEqual(1, server.MaxClients);
                Assert.AreEqual(1, server.NumClients);
                Assert.AreEqual(1, listener.ClientAccepted);

                server.ServerObject.StringProp = "foo";
                Thread.Sleep(SpinTime);
                Assert.AreEqual(client.ServerObject.StringProp, "foo");

                server.ServerObject.StringProp = "bar";
                Thread.Sleep(SpinTime);
                Assert.AreEqual(client.ServerObject.StringProp, "bar");

                GameClient<NetTestObject> faulted = new GameClient<NetTestObject>(Dns.GetHostName(), 6001, new NetTestObject(), () => new NetAuthObject(), flags);
                g.AddProcess(faulted);
                Thread.Sleep(SpinTime);
                g.RemoveProcess(faulted);
                Assert.IsTrue(faulted.IsFaulted);
                Assert.IsFalse(String.IsNullOrEmpty(faulted.FaultMessage));
                Thread.Sleep(SpinTime);
                Assert.AreEqual(1, listener.Sessions.Count);
                g.RemoveProcess(client);
                Thread.Sleep(SpinTime);
                Assert.AreEqual(0, listener.Sessions.Count);

                GameClient<NetTestObject> authFailure = new GameClient<NetTestObject>(Dns.GetHostName(), 6001, new NetTestObject(), () => new NetAuthObject { AuthFailure = true }, flags);
                g.AddProcess(authFailure);
                Thread.Sleep(SpinTime);
                g.RemoveProcess(authFailure);
                Assert.IsTrue(authFailure.IsFaulted);
                Assert.IsFalse(String.IsNullOrEmpty(authFailure.FaultMessage));
                Assert.AreEqual(0, listener.Sessions.Count);

                g.RemoveProcess(listener);
                g.RemoveProcess(server);
            }

            g.Stop();
        }

        void ConnectComplete(object asyncState)
        {
            _connectEvent.Set();
        }
    }
}
